package com.demarc.pesquisas.infraestrutura.util.utils;

import java.io.Serializable;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.List;

import javax.persistence.Id;

import org.apache.commons.beanutils.BeanUtils;
import org.apache.log4j.Logger;

import com.demarc.pesquisas.infraestrutura.persistencia.ObjetoPersistenteVersionado;

public class ReflectionUtils implements Serializable {

	private static final long serialVersionUID = 1L;

	private static final String ERRO_INSTANCIAR_CLASSE = "Não foi possível instanciar classe";
	private static final String ERRO_OBTER_VALOR_PROPRIEDADE = "Não foi possível obter o valor da propriedade: ";
	private static final String ERRO_ATRIBUIR_VALOR_PROPRIEDADE = "Não foi possível obter o valor da propriedade: ";

	private static final String UNCHECKED = "unchecked";

	private static final Logger LOGGER = Logger
			.getLogger(ReflectionUtils.class);

	public static void setFieldValue(Object object, String fieldName,
			Object value) {
		boolean configured = false;
		for (Class<?> clazz = object.getClass(); clazz != null; clazz = clazz
				.getSuperclass()) {
			try {
				Field field = clazz.getDeclaredField(fieldName);
				field.setAccessible(true);
				field.set(object, value);
				configured = true;
				break;
			} catch (Exception e) {
				LOGGER.info(e.getMessage());
			}
		}

		if (!configured) {
			LOGGER.error(ERRO_ATRIBUIR_VALOR_PROPRIEDADE + fieldName);
		}
	}

	public static Object getFieldValue(Object object, String fieldName) {
		if (object == null) {
			return null;
		}

		Object retrievedObject = null;
		boolean located = false;
		for (Class<?> clazz = object.getClass(); clazz != null; clazz = clazz
				.getSuperclass()) {
			try {
				Field field = clazz.getDeclaredField(fieldName);
				field.setAccessible(true);
				retrievedObject = field.get(object);
				located = true;
				break;
			} catch (Exception e) {
				// Continuar a busca na classe pai.
				LOGGER.info(e.getMessage());
			}
		}

		if (!located) {
			LOGGER.error(ERRO_OBTER_VALOR_PROPRIEDADE + fieldName);
		}

		return retrievedObject;
	}

	public static <F> F newInstance(Class<F> clazz) {
		try {
			return clazz.newInstance();
		} catch (Exception e) {
			LOGGER.error(ERRO_INSTANCIAR_CLASSE);
		}
		return null;
	}

	@SuppressWarnings(UNCHECKED)
	public static <F> F getPropertyAsObject(Object bean, String property) {
		F objeto = null;
		try {
			if (bean != null) {
				objeto = (F) org.apache.commons.beanutils.PropertyUtils
						.getProperty(bean, property);
			}
		} catch (Exception e) {
			throw new RuntimeException(e);
		}
		return objeto;
	}

	public static void preencherTodosCampos(List<Field> fields, Class<?> type) {
		for (Class<?> c = type; c != null; c = c.getSuperclass()) {
			for (Field field : c.getDeclaredFields()) {

				if (Modifier.isStatic(field.getModifiers())
						&& Modifier.isFinal(field.getModifiers())) {
					continue;
				}

				fields.add(field);
			}
		}
	}

	public static Object getValorId(ObjetoPersistenteVersionado entidade) {
		Object valor = null;
		for (Field field : entidade.getClass().getDeclaredFields()) {
			if (field.isAnnotationPresent(Id.class)) {
				valor = getFieldValue(entidade, field.getName());
				break;
			}
		}
		return valor;
	}

	public static Object[] getNomeCampoEValorId(
			ObjetoPersistenteVersionado entidade) {
		String nomeCampo = "";
		Object valorCampo = "";
		for (Field field : entidade.getClass().getDeclaredFields()) {
			if (field.isAnnotationPresent(Id.class)) {
				nomeCampo = field.getName();
				valorCampo = getFieldValue(entidade, field.getName());
				break;
			}
		}
		return new Object[] { nomeCampo, valorCampo };
	}

	@SuppressWarnings("unchecked")
	public static <T> T cloneBean(T objeto) {
		try {
			return (T) BeanUtils.cloneBean(objeto);
		} catch (Exception e) {
			throw new RuntimeException(e);
		}
	}

	public static <T> void copyProperties(T destino, T origem) {
		try {
			BeanUtils.copyProperties(destino, origem);
		} catch (Exception e) {
			throw new RuntimeException(e);
		}
	}
}